# -*- coding: utf-8 -*-

# FreeZapTV -- An application to turn your mobile device into a Freebox HD
# remote
#
# Copyright (C) 2010 Valéry Febvre <vfebvre@easter-eggs.com>
# http://code.google.com/p/freezaptv/
#
# This file is part of FreeZapTV.
#
# FreeZapTV is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# FreeZapTV is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""
FreeZapTV -- An application to turn your mobile device into a Freebox HD remote
"""

import cPickle, os.path
import ecore, elementary
import urllib

APP_VERSION = '1.0.0'
APP_NAME = 'FreeZapTV'
DATA_DIR = '/usr/share/freezaptv/'
#DATA_DIR = '../data'

DEFAULT_SETTINGS = {
    'code':       None,
    'box':        'hd1',
    'fullscreen': False,
    }

REMOTE_IMG_SIZE = (480, 640)
REMOTE_IMG_MAP = {
    (0, 72):    {'yellow': (70, 140), 'red': (160, 230), 'blue': (250, 320), 'green': (340, 410)},

    (84, 154):  {'power': (40, 80), 'home': (130, 230), 'list': (250, 350), 'tv': (370, 440)},

    (164, 200): {'1':    (20, 100), '2': (110, 190), '3':    (200, 280), 'info': (290, 370), 'info+long': (380, 460)},
    (210, 246): {'4':    (20, 100), '5': (110, 190), '6':    (200, 280), 'mail': (290, 370), 'mail+long': (380, 460)},
    (256, 292): {'7':    (20, 100), '8': (110, 190), '9':    (200, 280), 'help': (290, 370), 'help+long': (380, 460)},
    (304, 340): {'back': (20, 100), '0': (110, 190), 'swap': (200, 280), 'pip':  (290, 370), 'pip+long':  (380, 460)},

    (350, 404): {'vol_inc':  (40, 110), 'up':   (210, 270), 'prgm_inc': (370, 440)},
    (398, 452): {'left':    (140, 190), 'ok':   (200, 280), 'right':    (290, 340)},
    (446, 500): {'vol_dec':  (40, 110), 'down': (210, 270), 'prgm_dec': (370, 440)},

    (510, 552): {'bwd':  (70, 140), 'play': (160, 230), 'rec':  (250, 320), 'fwd':  (340, 410)},
    (562, 604): {'prev': (70, 140), 'stop': (160, 230), 'mute': (250, 320), 'next': (340, 410)},
}


class Icon(elementary.Icon):
    def __init__(self, parent, image):
        elementary.Icon.__init__(self, parent)
        self.file_set(os.path.join(DATA_DIR, 'images', image))


#
# Config
#

class Config():
    def __init__(self):
        cfg_dir = os.path.join(os.path.expanduser("~"), ".config/%s" % APP_NAME.lower())
        if not os.path.exists(cfg_dir):
            os.makedirs(cfg_dir, 0755)
        
        self.cfg_path = os.path.join(cfg_dir, '%s.conf' % APP_NAME.lower())
        if not os.path.exists(self.cfg_path):
            self.data = DEFAULT_SETTINGS
        else:
            self.data = cPickle.load(open(self.cfg_path, 'r'))

        self.is_dirty = False

    def save(self):
        if self.is_dirty:
            cPickle.dump(self.data, open(self.cfg_path, 'w+'))
            self.is_dirty = False

    def get(self, name):
        if name not in self.data.keys():
            # a new setting not available in user settings yet
            self.data[name] = DEFAULT_SETTINGS[name]
        return self.data[name]

    def set(self, name, value):
        if self.data[name] != value:
            self.data[name] = value
            self.is_dirty = True

cfg = Config()


#
# Remote page
#

class RemotePage(object):
    def __init__(self, main):
        self.main = main
        self.long_timer = None

        self.layout = elementary.Layout(self.main.win)
        self.layout.file_set(os.path.join(DATA_DIR, 'freezaptv.edj'), 'remote')
        self.layout.show()

        self.bg = self.layout.edje_get()
        self.bg.on_mouse_down_add(self.on_mouse_down)
        self.bg.on_mouse_up_add(self.on_mouse_up)

        self.main.pager.content_push(self.layout)

    def convert_pos_to_key(self, pos):
        factor = self.main.win.size[1] / float(REMOTE_IMG_SIZE[1])
        x = pos[0]
        y = pos[1] / factor
        for y_range, row_keys in REMOTE_IMG_MAP.iteritems():
            if y >= y_range[0] and y <= y_range[1]:
                for row_key, x_range in row_keys.iteritems():
                    if x >= x_range[0] and x <= x_range[1]:
                        return row_key
        return None

    def on_mouse_down(self, bg, event):
        self.handle_mouse_up = True
        pos = (event.position.canvas[0], event.position.canvas[1])
        self.long_timer = ecore.timer_add(.5, self.remote_control, pos, True)

    def on_mouse_up(self, bg, event):
        if self.long_timer:
            self.long_timer.delete()
            self.long_timer = None
        if not self.handle_mouse_up:
            return

        pos = event.position.canvas
        if pos.x <= 60 and self.main.win.size[1] - pos.y <= 120:
            self.main.show_settings_page()
        elif self.main.win.size[0] - pos.x <= 60 and self.main.win.size[1] - pos.y <= 120:
            self.main.quit()
        else:
            self.remote_control(pos)

    def promote(self):
        self.main.pager.content_promote(self.layout)

    def remote_control(self, pos, long_press = False):
        if long_press:
            self.handle_mouse_up = False

        code = cfg.get('code')
        if code is None:
            return
        key = self.convert_pos_to_key(pos)
        if key:
            url = 'http://%s.freebox.fr/pub/remote_control' % cfg.get('box')
            url += '?code=' + cfg.get('code')
            if key.endswith('+long') or long_press:
                key = key.split('+')[0]
                url += '&long=true'
            url += '&key=' + key
            try:
                urllib.urlopen(url)
            except:
                print url
                print 'Erreur: la Freebox HD est eteinte ou votre code Freebox est invalide'


#
# Settings page
#

class SettingsPage(object):
    def __init__(self, main):
        self.main = main
        self.box = None

    def build(self):
        self.box = elementary.Box(self.main.win)
        self.box.size_hint_align_set(-1, -1)
        self.box.show()

        scroller = elementary.Scroller(self.main.win)
        scroller.bounce_set(False, False)
        scroller.size_hint_weight_set(1.0, 1.0)
        scroller.size_hint_align_set(-1.0, -1.0)
        self.box.pack_end(scroller)
        scroller.show()

        box = elementary.Box(self.main.win)
        box.size_hint_weight_set(1, 1)
        scroller.content_set(box)
        box.show()

        # code
        box_code = elementary.Box(self.main.win)
        box_code.horizontal_set(True)
        box_code.size_hint_weight_set(1, 0)
        box_code.size_hint_align_set(-1, 0)
        box.pack_end(box_code)
        box_code.show()

        label_code = elementary.Label(self.main.win)
        label_code.size_hint_weight_set(1.0, 1.0)
        label_code.size_hint_align_set(-1, 0.5)
        label_code.label_set('Code télécommande')
        box_code.pack_end(label_code)
        label_code.show()

        entry_code = elementary.Entry(self.main.win)
        entry_code.single_line_set(True)
        entry_code.size_hint_weight_set(1.0, 0.0)
        entry_code.size_hint_align_set(-1.0, 0.0)
        entry_code.entry_set(cfg.get('code') or '')
        entry_code.callback_changed_add(self.update_code)
        box_code.pack_end(entry_code)
        entry_code.show()

        # fullscreen
        toggle = elementary.Toggle(self.main.win)
        toggle.label_set("Plein écran")
        toggle.size_hint_align_set(-1, 0)
        toggle.state_set(cfg.get('fullscreen'))
        toggle.states_labels_set("Oui", "Non")
        toggle.callback_changed_add(self.toggle_fullscreen)
        box.pack_end(toggle)
        toggle.show()

        # buttons
        box_btns = elementary.Box(self.main.win)
        box_btns.horizontal_set(True)
        box_btns.homogenous_set(True)
        box_btns.size_hint_align_set(-1.0, 0)
        self.box.pack_end(box_btns)
        box_btns.show()

        btn_remote = elementary.Button(self.main.win)
        btn_remote.label_set('Télécommande')
        btn_remote.icon_set(Icon(self.main.win, 'remote-96.png'))
        btn_remote.size_hint_weight_set(1, 0)
        btn_remote.size_hint_align_set(-1, 0)
        btn_remote.callback_clicked_add(self.main.show_remote_page)
        box_btns.pack_end(btn_remote)
        btn_remote.show()

        btn_about = elementary.Button(self.main.win)
        btn_about.label_set('À propos')
        btn_about.icon_set(Icon(self.main.win, 'about.png'))
        btn_about.size_hint_weight_set(1, 0)
        btn_about.size_hint_align_set(-1, 0)
        btn_about.callback_clicked_add(self.main.show_about_page)
        box_btns.pack_end(btn_about)
        btn_about.show()

        self.main.pager.content_push(self.box)

    def promote(self):
        if not self.box:
            self.build()
        self.main.pager.content_promote(self.box)

    def toggle_fullscreen(self, toggle):
        fullscreen = toggle.state_get()
        if fullscreen != cfg.get('fullscreen'):
            self.main.win.fullscreen_set(fullscreen)
            cfg.set('fullscreen', fullscreen)

    def update_code(self, entry):
        cfg.set('code', entry.entry_get())


#
# About page
#

class AboutPage(object):
    def __init__(self, main):
        self.main = main
        self.box = None

    def build(self):
        self.box = elementary.Box(self.main.win)
        self.box.size_hint_align_set(-1, -1)
        self.box.show()

        label = elementary.Label(self.main.win)
        label.label_set('<b>%s %s</>' % (APP_NAME, APP_VERSION))
        label.size_hint_align_set(0.5, 0.5)
        self.box.pack_end(label)
        label.show()

        scroller = elementary.Scroller(self.main.win)
        scroller.bounce_set(False, True)
        scroller.size_hint_weight_set(1.0, 1.0)
        scroller.size_hint_align_set(-1.0, -1.0)
        self.box.pack_end(scroller)
        scroller.show()

        box = elementary.Box(self.main.win)
        box.size_hint_weight_set(1, 1)
        scroller.content_set(box)
        box.show()

        about  = """\
<b>FreeZapTV</b> transforme votre mobile en télécommande de Freebox HD.

<b>Copyright</> © 2010 Valéry (aka valos) Febvre

<b>Licence</> GNU GPL v3

<b>Homepage</> http://code.google.com/p/freezaptv/

Si vous aimez FreeZapTV, envoyez-moi un courriel à vfebvre@easter-eggs.com

Happy zapping\
"""

        entry = elementary.Entry(self.main.win)
        entry.editable_set(False)
        entry.line_wrap_set(True)
        entry.size_hint_align_set(-1, -1)
        entry.entry_set(about.replace('\n', '<br>'))
        box.pack_end(entry)
        entry.show()

        # buttons
        box_btns = elementary.Box(self.main.win)
        box_btns.horizontal_set(True)
        box_btns.homogenous_set(True)
        box_btns.size_hint_align_set(-1.0, 0)
        self.box.pack_end(box_btns)
        box_btns.show()

        btn_remote = elementary.Button(self.main.win)
        btn_remote.label_set('Télécommande')
        btn_remote.icon_set(Icon(self.main.win, 'remote-96.png'))
        btn_remote.size_hint_weight_set(1, 0)
        btn_remote.size_hint_align_set(-1, 0)
        btn_remote.callback_clicked_add(self.main.show_remote_page)
        box_btns.pack_end(btn_remote)
        btn_remote.show()

        btn_settings = elementary.Button(self.main.win)
        btn_settings.label_set('Réglages')
        btn_settings.icon_set(Icon(self.main.win, 'settings.png'))
        btn_settings.size_hint_weight_set(1, 0)
        btn_settings.size_hint_align_set(-1, 0)
        btn_settings.callback_clicked_add(self.main.show_settings_page)
        box_btns.pack_end(btn_settings)
        btn_settings.show()

        self.main.pager.content_push(self.box)

    def promote(self):
        if not self.box:
            self.build()
        self.main.pager.content_promote(self.box)


class FreeZapTV(object):
    def __init__(self):
        self.win = elementary.Window(APP_NAME, elementary.ELM_WIN_BASIC)
        self.win.title_set(APP_NAME)
        self.win.fullscreen_set(cfg.get('fullscreen'))
        self.win.callback_destroy_add(self.quit)

        bg = elementary.Background(self.win)
        self.win.resize_object_add(bg)
        bg.size_hint_weight_set(1, 1)
        bg.show()

        self.pager = elementary.Pager(self.win)
        self.pager.size_hint_weight_set(1.0, 1.0)
        self.win.resize_object_add(self.pager)
        self.pager.show()

        self.remote_page = RemotePage(self)
        self.settings_page = SettingsPage(self)
        self.about_page = AboutPage(self)

        self.show_remote_page()

        self.win.resize(480, 640)
        self.win.show()

    def quit(self, *args):
        # save settings
        cfg.save()
        # exit
        elementary.exit()

    def show_remote_page(self, *args):
        self.remote_page.promote()

    def show_settings_page(self, *args):
        self.settings_page.promote()

    def show_about_page(self, *args):
        self.about_page.promote()


def main():
    elementary.init()
    FreeZapTV()
    elementary.run()
    elementary.shutdown()
    return 0

if __name__ == "__main__":
    exit(main())
